package com.upb.webserver.host;

import cn.hutool.core.collection.CollectionUtil;
import com.upb.webserver.chainhost.ChainHostManager;
import com.upb.webserver.common.constant.Constant;
import com.upb.webserver.common.enums.HostOperateMethodEnum;
import com.upb.webserver.common.enums.HostStatusEnum;
import com.upb.webserver.common.enums.PlatformEnum;
import com.upb.webserver.common.enums.RoleTypeEnum;
import com.upb.webserver.common.exception.WebServerException;
import com.upb.webserver.common.pojo.base.BasePageResponse;
import com.upb.webserver.common.pojo.host.ReqAddHostVO;
import com.upb.webserver.common.pojo.host.ReqUpdateHostVO;
import com.upb.webserver.common.properties.WebServerProperties;
import com.upb.webserver.common.returncode.ConstantCode;
import com.upb.webserver.common.tools.JsonUtils;
import com.upb.webserver.common.tools.MybatisExampleTools;
import com.upb.webserver.common.tools.cmd.ExecuteResult;
import com.upb.webserver.common.tools.cmd.JavaCommandExecutor;
import com.upb.webserver.company.CompanyDeveloperManager;
import com.upb.webserver.company.CompanyDeveloperService;
import com.upb.webserver.dao.entity.TbHost;
import com.upb.webserver.dao.entity.TbHostExample;
import com.upb.webserver.dao.entity.TbManagerExample;
import com.upb.webserver.dao.entity.TbOrg;
import com.upb.webserver.dao.mapper.TbHostMapper;
import com.upb.webserver.order.WorkOrderManager;
import com.upb.webserver.org.OrgManager;
import com.upb.webserver.shell.ShellService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class HostService {
    @Autowired
    private TbHostMapper hostMapper;
    @Autowired
    private HostManager hostManager;
    @Autowired
    private OrgManager orgManager;
    @Autowired
    private WorkOrderManager workOrderManager;
    @Autowired
    private CompanyDeveloperService companyDeveloperService;
    @Autowired
    private ChainHostManager chainHostManager;
    @Autowired
    private WebServerProperties properties;
    @Autowired
    private ShellService shellService;
    @Autowired
    private CompanyDeveloperManager companyDeveloperManager;

    /**
     * add new Host.
     */
    public TbHost addHost(byte platform, int currentAccountId, ReqAddHostVO param) throws WebServerException {
        log.info("start exec method [addHost]. platform:{} currentAccountId:{} param:{}", platform, currentAccountId, JsonUtils.objToString(param));
        dos2unix();

        //get companyId
        Integer belongLongToCompanyId = null;
        if (PlatformEnum.DEVELOPER.getId() == platform) {
            belongLongToCompanyId = companyDeveloperService.selectCompanyIdByDeveloperId(currentAccountId);
        } else if (PlatformEnum.DEV_OPS.getId() == platform) {
            TbOrg tbOrg = orgManager.verifyOrgId(param.getOrgId());
            belongLongToCompanyId = tbOrg.getCompanyId();
        } else {
            log.warn("fail exec method[addHost]. not support platform:{}", platform);
            throw new WebServerException(ConstantCode.PLATFORM_NOT_SUPPORT);
        }

        Pair<Boolean, String> restPair = null;
        try {
            if (!properties.getOpenDebug()) {
                ExecuteResult result = shellService.execHostOperate(param.getIp(), param.getSshPort(), param.getSshUser(), "", HostOperateMethodEnum.CHECK_HOST.getName());
                restPair = Pair.of(result.success(), result.getExecuteOut());
            } else {
                log.info("is debug:{}", properties.getOpenDebug());
                restPair = Pair.of(true, "test");
            }

        } catch (Exception ex) {
            log.warn("fail exec method [addHost].", ex);
            restPair = Pair.of(false, ex.getMessage());
        }

        log.info("restPair:{}", JsonUtils.objToString(restPair));
        if (Objects.isNull(restPair) || !restPair.getLeft()) {
            log.info("fail exec method [addHost]. key:{} message:{}", restPair.getLeft(), restPair.getRight());
            throw new WebServerException(ConstantCode.HOST_CHECK_NOT_SUCCESS.msg(restPair.getRight()));
        }


        TbHostExample example = MybatisExampleTools.initSampleExample(TbHostExample.class, new TbHost(param.getIp(), param.getOrgId()));
        if (hostMapper.countByExample(example) > 0) {
            log.warn("fail exec method[addHost]. found record by orgId:{} and hostIp:{}", param.getOrgId(), param.getIp());
            throw new WebServerException(ConstantCode.FOUND_RECORD_BY_ORG_AND_IP);
        }

        //save developer
        TbHost tbHost = new TbHost();
        BeanUtils.copyProperties(param, tbHost);
        tbHost.setStatus(HostStatusEnum.NORMAL.getId());
        tbHost.setCreateFromPlatform(platform);
        tbHost.setCreateBy(currentAccountId);
        tbHost.setCompanyId(belongLongToCompanyId);
        tbHost.setGmtCreate(new Date());
        tbHost.setGmtModified(new Date());

        hostMapper.insertSelective(tbHost);

        TbHost hostRsp = hostMapper.selectByPrimaryKey(tbHost.getId());
        log.info("success exec method [addHost]");
        return hostRsp;
    }

    /**
     * update by id.
     *
     * @param param
     */
    public void updateHost(ReqUpdateHostVO param) {
        log.info("start exec method [updateHost]. param:{}", JsonUtils.objToString(param));

        //check id
        TbHost tbHost = hostManager.verifyHostId(param.getId());

        if (isHostInUse(param.getId())) {
            log.warn("fail exec method [deleteByHostId]. hostId:{} is in use", param.getId());
            throw new WebServerException(ConstantCode.HOST_HAD_BEAN_IN_USE);
        }

        //update
        BeanUtils.copyProperties(param, tbHost);
        hostMapper.updateByPrimaryKey(tbHost);
        log.info("success exec method [updateHost]");
    }


    /**
     *
     */
    private void dos2unix() {
        List<String> shells = Arrays.asList(Constant.SHELL_CHECK_HOST, Constant.SCP_SHELL, Constant.SHELL_HOST_OPERATE);

        for (String shell : shells) {
            //dos2unix
            String command = String.format("dos2unix %1s", shell);
            JavaCommandExecutor.executeCommand(command, Constant.EXEC_SHELL_TIMEOUT);
        }
    }

    /**
     * @param platform
     * @param currentAccountId
     * @param pageNumber
     * @param pageSize
     * @param orgIdArr
     * @return
     */
    public BasePageResponse queryHostByPage(byte platform, String currentAccountRole, int currentAccountId, int pageNumber, int pageSize, List<Integer> orgIdArr) {
        log.info("start exec method [queryHostByPage].currentAccountRole:{} currentAccountId:{} pageNumber:{} pageSize:{} orgIdArr:{}", currentAccountRole, currentAccountId, pageNumber, pageSize, JsonUtils.objToString(orgIdArr));

        Map<String, Object> inParam = new HashMap<>();
        if (CollectionUtil.isNotEmpty(orgIdArr)) {
            inParam.put("orgId", orgIdArr);
        }

        TbHost hostParam = new TbHost();
        if (platform == PlatformEnum.DEVELOPER.getId()) {
            int companyId = companyDeveloperService.selectCompanyIdByDeveloperId(currentAccountId);
            hostParam = new TbHost(companyId);
        }

        TbHostExample example = MybatisExampleTools.initSamplePageExample(TbHostExample.class, pageNumber, pageSize, hostParam, inParam);


        if (RoleTypeEnum.API_MANAGER.getName().equalsIgnoreCase(currentAccountRole)) {
            List<Integer> companyIdList = companyDeveloperManager.listCompanyIdByPlatformAndAccountId(platform, currentAccountId);
            if (CollectionUtil.isEmpty(companyIdList))
                return BasePageResponse.initialize();

            TbHostExample.Criteria criteria = example.createCriteria();
            criteria.andCompanyIdIn(companyIdList);
            example.getOredCriteria().add(criteria);
        }


        BasePageResponse pageResponse = hostManager.queryHostByPage(example);

        log.info("success exec method [queryHostByPage]. result:{}", JsonUtils.objToString(pageResponse));
        return pageResponse;
    }

    /**
     * @param companyId
     * @return
     */
    public Set<Integer> getHostIdSetByCompanyId(int companyId) {
        log.info("start exec method [getHostIdSetByCompanyId]. companyId:{}", companyId);

        TbHost hostParam = new TbHost(companyId);
        TbHostExample example = MybatisExampleTools.initSampleExample(TbHostExample.class, hostParam);
        List<TbHost> orgList = hostMapper.selectByExample(example);

        if (CollectionUtil.isEmpty(orgList)) {
            log.warn("fail exec method[getHostIdSetByCompanyId]. not found org by companyId:{}", companyId);
            return CollectionUtil.newHashSet();
        }
        Set<Integer> listOrgId = orgList.stream().map(o -> o.getId()).collect(Collectors.toSet());
        log.info("success exec method [getHostIdSetByCompanyId]. companyId:{} result:{}", companyId, JsonUtils.objToString(listOrgId));
        return listOrgId;
    }


    /**
     * @param hostId
     */
    public void deleteByHostId(int hostId) {
        log.info("start exec method [deleteByHostId]. hostId:{}", hostId);

        //check hostId
        hostManager.verifyHostId(hostId);
        if (isHostInUse(hostId)) {
            log.warn("fail exec method [deleteByHostId]. hostId:{} is in use", hostId);
            throw new WebServerException(ConstantCode.HOST_HAD_BEAN_IN_USE);
        }

        hostMapper.deleteByPrimaryKey(hostId);
        log.info("start exec method [deleteByHostId]. HostId:{}", hostId);
    }

    /**
     * @param hostId
     * @return
     */
    public boolean isHostInUse(int hostId) {
        log.info("start exec method [isHostInUse]. hostId:{}", hostId);

        //check from chain-host
        long countHostInChain = chainHostManager.countByHostId(hostId);
        if (countHostInChain > 0) {
            log.info("finish exec method[isHostInUse]. hostId:{} result:{}", hostId, true);
            return true;
        }

        //check from workOrder
        Set<Integer> hostUseInWorkOrderSet = workOrderManager.getAllHostIdFromWorkOrderOfNewChain();
        if (CollectionUtil.isNotEmpty(hostUseInWorkOrderSet) && hostUseInWorkOrderSet.contains(hostId)) {
            log.info("finish exec method[isHostInUse]. hostId:{} result:{}", hostId, true);
            return true;
        }

        log.info("success exec method [isHostInUse]. hostId:{} result:{}", hostId, false);
        return false;
    }
}
